/*
 * TextureManager.cpp
 *
 *  Created on: 2009/01/13
 *      Author: JP10161
 */

#include "TextureManager.h"
#include "RenderDevice.h"


extern "C" {
#define XMD_H
#include "jpeglib.h"
}


template<> TextureManager* ISingleton<TextureManager>::m_pInstance = 0;


const String TextureManager::_EXT_PNG			= _T("png");
const String TextureManager::_EXT_JPG			= _T("jpg");
const String TextureManager::_EXT_JPEG			= _T("jpeg");
const String TextureManager::_EXT_GIF			= _T("gif");
const String TextureManager::_EXT_BMP			= _T("bmp");


TextureManager::TextureManager()
{
	// TODO Auto-generated constructor stub

}

TextureManager::~TextureManager()
{
	// TODO Auto-generated destructor stub
	RenderDevice& rd = RenderDevice::GetInstance();
	mTextures.clear();

	rd.cleanupTextures();
}

Texture* TextureManager::load(const String& p)
{
	Texture* t = isExists(p);
	if(t)
		return t;

	t = loadBitmap(p);
	if(t)
	{
		mTextures.push_back(t);
	}

	return t;
}

Texture* TextureManager::isExists(const String& p)
{
	for(vector<Texture*>::iterator it=mTextures.begin();it!=mTextures.end();++it)
	{
		Texture* t = (*it);
		if(t && t->getPath() == p)
		{
			return t;
		}
	}

	return 0;
}

Texture* TextureManager::loadBitmap(const String& p)
{
	size_t idx = p.find_last_of(_STR_DOT);
	if(idx == String::npos)
		return 0;

	String ext = p.substr(idx + 1);

	if(_tcsicmp(ext.c_str(), _EXT_PNG.c_str()) == 0)
	{
		return loadPng(p);
	}

	if(_tcsicmp(ext.c_str(), _EXT_JPG.c_str()) == 0 || _tcsicmp(ext.c_str(), _EXT_JPEG.c_str()) == 0)
	{
		return loadJpg(p);
	}

	if(_tcsicmp(ext.c_str(), _EXT_GIF.c_str()) == 0)
	{
		return loadGif(p);
	}

	return 0;
}

Texture* TextureManager::loadJpg(const String& p)
{
	FILE *fp;

#ifdef UNICODE
	char mbstr[512]; memset(mbstr, 0, 512);
	::WideCharToMultiByte(CP_UTF8, 0, p.c_str(), (int32)p.size(), mbstr, 512, 0, 0);
	if((fp = fopen(mbstr, "rb")) == NULL)
#else
	if((fp = fopen(p.c_str(), "rb")) == NULL)
#endif
		AltoThrow(Exception::ERR_IO);

	jpeg_decompress_struct cinfo;
	jpeg_error_mgr jerr;

	cinfo.err = jpeg_std_error(&jerr);    // set up the normal JPEG error routines.
	cinfo.client_data = (void*)fp;       // set the stream into the client_data

	// Now we can initialize the JPEG decompression object.
	jpeg_create_decompress(&cinfo);

	jpeg_stdio_src(&cinfo, fp);

	// Read file header, set default decompression parameters
	jpeg_read_header(&cinfo, true);

	uint32 width = cinfo.image_width;
	uint32 height = cinfo.image_height;

	uint32 format = Texture::COLOR_TYPE_RGB;
	switch(cinfo.out_color_space)
	{
	case JCS_GRAYSCALE:
		format = Texture::COLOR_TYPE_GRAY;
		break;
	case JCS_RGB:
		format = Texture::COLOR_TYPE_RGB;
		break;
	default:
		fclose(fp);
		jpeg_destroy_decompress(&cinfo);
		AltoThrow(Exception::ERR_FILE);
	}

	jpeg_calc_output_dimensions(&cinfo);

	// Start decompressor
	jpeg_start_decompress(&cinfo);

	// Set up the row pointers...
	uint32 rowBytes = cinfo.output_width * cinfo.output_components;
	uint8* ucbuf = new uint8[height * rowBytes];
	if(!ucbuf)
	{
		fclose(fp);
		AltoThrow(Exception::ERR_MEMORY);
	}

	uint8* linha = ucbuf;
	while(cinfo.output_scanline < cinfo.output_height)
	{
		jpeg_read_scanlines(&cinfo, &linha, 1);
		linha = ucbuf + rowBytes * cinfo.output_scanline;
	}

	// Finish decompression
	jpeg_finish_decompress(&cinfo);

	// Release JPEG decompression object
	// This is an important step since it will release a good deal of memory.
	jpeg_destroy_decompress(&cinfo);

	//get render device instance
	RenderDevice& rd = RenderDevice::GetInstance();

	Texture* t = 0;
	uint32 bytesPerPixel = cinfo.output_components;
	uint32 scalew, scaleh;
	if(isScaleNeeded(width, height, scalew, scaleh))
	{
		uint8* uscaledbuf = new uint8 [scalew * scaleh * bytesPerPixel];
		if(!uscaledbuf)
		{
			fclose(fp);
			SAFE_DELETE_ARRAY(ucbuf);
			AltoThrow(Exception::ERR_MEMORY);
		}

		Texture::scaleTexture(width, height, rowBytes, format, ucbuf, scalew, scaleh, uscaledbuf);
		t = rd.createTexture(p, scalew, scaleh, width, height, rowBytes, format, uscaledbuf, ucbuf);

		SAFE_DELETE_ARRAY(uscaledbuf);
	}
	else
	{
		t = rd.createTexture(p, width, height, width, height, rowBytes, format, ucbuf, ucbuf);
	}

	SAFE_DELETE_ARRAY(ucbuf);

	fclose(fp);

	return t;
}

Texture* TextureManager::loadPng(const String& p)
{
	png_structp png_ptr;
	png_infop info_ptr;

	unsigned int sig_read = 8;
	png_uint_32 width, height;
	int bit_depth, color_type, interlace_type;

	FILE *fp;

#ifdef UNICODE
	char mbstr[512]; memset(mbstr, 0, 512);
	::WideCharToMultiByte(CP_UTF8, 0, p.c_str(), (int32)p.size(), mbstr, 512, 0, 0);
	if((fp = fopen(mbstr, "rb")) == NULL)
#else
	if((fp = fopen(p.c_str(), "rb")) == NULL)
#endif
		AltoThrow(Exception::ERR_IO);

	uint8* header = new uint8 [sig_read];

	fread(header, 1, sig_read, fp);
	bool is_png = png_check_sig(header, sig_read) ? true : false;
	if(!is_png)
	{
		SAFE_DELETE_ARRAY(header);
		AltoThrow(Exception::ERR_FILE);
	}

	SAFE_DELETE_ARRAY(header);

	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)0, 0, 0);
	if(!png_ptr)
	{
		fclose(fp);
		AltoThrow(Exception::ERR_IO);
	}

#if defined(PNG_LIBPNG_VER) && (PNG_LIBPNG_VER >= 10200)
	png_uint_32 mask, flags;

	flags = png_get_asm_flags(png_ptr);
	mask = png_get_asm_flagmask(PNG_SELECT_READ | PNG_SELECT_WRITE);
	png_set_asm_flags(png_ptr, flags | mask);
#endif

	/* Allocate/initialize the memory for image information.  REQUIRED. */
	info_ptr = png_create_info_struct(png_ptr);
	if(!info_ptr)
	{
		fclose(fp);
		png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
		AltoThrow(Exception::ERR_MEMORY);
	}

	/* Set error handling if you are using the setjmp/longjmp method (this is
	* the normal method of doing things with libpng).  REQUIRED unless you
	* set up your own error handlers in the png_create_read_struct() earlier.
	*/

	if (setjmp(png_jmpbuf(png_ptr)))
	{
		/* Free all of the memory associated with the png_ptr and info_ptr */
		png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
		fclose(fp);
		/* If we get here, we had a problem reading the file */
		AltoThrow(Exception::ERR_IO);
	}

	png_init_io(png_ptr, fp);

	//Read off the info on the image.
	png_set_sig_bytes(png_ptr, sig_read);
	png_set_read_fn(png_ptr, (png_voidp)fp, &pngReadDataFn);
	png_read_info(png_ptr, info_ptr);
	png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, 0, 0);

	if(color_type == PNG_COLOR_TYPE_PALETTE)
		png_set_palette_to_rgb(png_ptr);

	// Convert low bit colors to 8 bit colors
	if(bit_depth < 8)
	{
		if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
			png_set_gray_1_2_4_to_8(png_ptr);
		else
			png_set_packing(png_ptr);
	}

	if(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
		png_set_tRNS_to_alpha(png_ptr);

	if(bit_depth == 16)
		png_set_strip_16(png_ptr);

	if(color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
		png_set_gray_to_rgb(png_ptr);

	png_read_update_info(png_ptr, info_ptr);
	png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, 0, 0);

	png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, 0, 0);
	png_uint_32 rowBytes = png_get_rowbytes(png_ptr, info_ptr);

	uint32 format = Texture::COLOR_TYPE_RGBA;
	uint32 bytesPerPixel = 4;
	switch(color_type)
	{
	case PNG_COLOR_TYPE_RGB_ALPHA:
		format = Texture::COLOR_TYPE_RGBA;
		bytesPerPixel = 4;
		break;
	case PNG_COLOR_TYPE_RGB:
		format = Texture::COLOR_TYPE_RGB;
		bytesPerPixel = 3;
		break;
	case PNG_COLOR_TYPE_GRAY:
		format = Texture::COLOR_TYPE_GRAY;
		bytesPerPixel = 1;
		break;
	default:
		png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
		fclose(fp);
		AltoThrow(Exception::ERR_FILE);
	};

	uint8* ucbuf = new uint8[height * rowBytes];
	if(!ucbuf)
	{
		png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
		fclose(fp);
		AltoThrow(Exception::ERR_MEMORY);
	}

    png_bytepp row_ptr = new uint8* [height];
    if(!row_ptr)
    {
		png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
		fclose(fp);
		AltoThrow(Exception::ERR_MEMORY);
    }

    for(uint32 h=0;h<height;h++)
    	row_ptr[h] = ucbuf + h * rowBytes;

    png_read_image(png_ptr, row_ptr);
    png_read_end(png_ptr, NULL);

	SAFE_DELETE_ARRAY(row_ptr);

	//get the render device instance
	RenderDevice& rd = RenderDevice::GetInstance();

	Texture* t = 0;
	uint32 scalew, scaleh;
	//check if it is need to scale image
	if(isScaleNeeded(width, height, scalew, scaleh))
	{
		uint8* uscaledbuf = new uint8 [scalew * scaleh * bytesPerPixel];
		if(!uscaledbuf)
		{
			SAFE_DELETE_ARRAY(ucbuf);
			png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
			fclose(fp);
			AltoThrow(Exception::ERR_MEMORY);
		}

		Texture::scaleTexture(width, height, rowBytes, format, ucbuf, scalew, scaleh, uscaledbuf);
		t = rd.createTexture(p, scalew, scaleh, width, height, rowBytes, format, uscaledbuf, ucbuf);

		SAFE_DELETE_ARRAY(uscaledbuf);
	}
	else
	{
		//there is no need to scale image.
		t = rd.createTexture(p, width, height, width, height, rowBytes, format, ucbuf, ucbuf);
	}

	SAFE_DELETE_ARRAY(ucbuf);

	png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);

	fclose(fp);

	return t;
}

void TextureManager::pngReadDataFn(png_structp png_ptr, png_bytep data, png_size_t length)
{
	fread(data, length, 1, (FILE*)png_get_io_ptr(png_ptr));
}

int32 TextureManager::gifReadDataFn(GifFileType* gifinfo, GifByteType* data, int32 length)
{
	return (int32)fread(data, length, 1, (FILE*)gifinfo->UserData);
}

Texture* TextureManager::loadGif(const String& p)
{
	FILE *fp;

#ifdef UNICODE
	char mbstr[512]; memset(mbstr, 0, 512);
	::WideCharToMultiByte(CP_UTF8, 0, p.c_str(), (int32)p.size(), mbstr, 512, 0, 0);
	if((fp = fopen(mbstr, "rb")) == NULL)
#else
	if((fp = fopen(p.c_str(), "rb")) == NULL)
#endif
		AltoThrow(Exception::ERR_IO);

	GifFileType *gifinfo = DGifOpen((void*)fp, &gifReadDataFn);
	if(!gifinfo)
		return 0;

	GifRecordType recordType;
	do
	{
		if(DGifGetRecordType(gifinfo, &recordType) == GIF_ERROR)
			break;

		if(recordType == IMAGE_DESC_RECORD_TYPE)
		{
			if(DGifGetImageDesc(gifinfo) == GIF_ERROR)
				break;

			uint32 format = (gifinfo->SBackGroundColor == 0 ) ? Texture::COLOR_TYPE_RGB : Texture::COLOR_TYPE_RGBA;
			uint32 width = (uint32)gifinfo->SWidth;
			uint32 height = (uint32)gifinfo->SHeight;
			uint32 bytesPerPixel = 4;

			switch(format)
			{
			case Texture::COLOR_TYPE_RGB:
				bytesPerPixel = 3;
				break;
			case Texture::COLOR_TYPE_RGBA:
				bytesPerPixel = 4;
				break;
			}

			uint32 rowBytes = width * bytesPerPixel;
			uint8* ucbuf = new uint8[rowBytes * height];
			if(!ucbuf)
			{
				DGifCloseFile(gifinfo);
				fclose(fp);
				AltoThrow(Exception::ERR_MEMORY);
			}

			uint32 gwidth = gifinfo->Image.Width ? gifinfo->Image.Width : width;
			uint32 gheight= gifinfo->Image.Height ? gifinfo->Image.Height : height;
			uint32 gsize = width * height;
			uint8* indexes   = new uint8[gsize];
			if(!indexes)
			{
				DGifCloseFile(gifinfo);
				fclose(fp);
				AltoThrow(Exception::ERR_MEMORY);
			}

			if(DGifGetLine(gifinfo, indexes, gsize) != GIF_ERROR)
			{
				// use the global or local color table ?
				GifColorType* color = gifinfo->SColorMap->Colors;
				if(gifinfo->Image.ColorMap)
					color = gifinfo->Image.ColorMap->Colors;

				if(color)
				{
					uint8* dst = ucbuf;
					uint8* src = indexes;
					uint32 right  = gifinfo->Image.Left + gwidth;
					uint32 bottom = gifinfo->Image.Top + gheight;
					uint32 next   = (width - gwidth) * bytesPerPixel;

					if(format == Texture::COLOR_TYPE_RGBA)
					{
						for(uint32 y=gifinfo->Image.Top; y<bottom; y++)
						{
							for(uint32 x=gifinfo->Image.Left; x<right; x++, src++)
							{
								if(*src == gifinfo->SBackGroundColor)
								{
									// this is a transparent pixel
									dst[0] = 0;    // red
									dst[1] = 0;    // green
									dst[2] = 0;    // blue
									dst[3] = 0;    // alpha
								}
								else
								{
									dst[0] = color[*src].Red;
									dst[1] = color[*src].Green;
									dst[2] = color[*src].Blue;
									dst[3] = 0;    // alpha
								}
								ucbuf += bytesPerPixel;
							}
							ucbuf += next;
						}
					}
					else
					{
						for(uint32 y=gifinfo->Image.Top; y<bottom; y++)
						{
							for(uint32 x=gifinfo->Image.Left; x<right; x++, src++)
							{
								dst[0] = color[*src].Red;
								dst[1] = color[*src].Green;
								dst[2] = color[*src].Blue;
								dst += bytesPerPixel;
							}
							dst += next;
						}
					}

					SAFE_DELETE_ARRAY(indexes);
					DGifCloseFile(gifinfo);

					//get the render device instance
					RenderDevice& rd = RenderDevice::GetInstance();

					Texture* t = 0;
					uint32 scalew, scaleh;
					//check if it is need to scale image
					if(isScaleNeeded(width, height, scalew, scaleh))
					{
						uint8* uscaledbuf = new uint8 [scalew * scaleh * bytesPerPixel];
						if(!uscaledbuf)
						{
							SAFE_DELETE_ARRAY(ucbuf);
							fclose(fp);
							AltoThrow(Exception::ERR_MEMORY);
						}

						Texture::scaleTexture(width, height, rowBytes, format, ucbuf, scalew, scaleh, uscaledbuf);
						t = rd.createTexture(p, scalew, scaleh, width, height, rowBytes, format, uscaledbuf, ucbuf);

						SAFE_DELETE_ARRAY(uscaledbuf);
					}
					else
					{
						//there is no need to scale image.
						t = rd.createTexture(p, width, height, width, height, rowBytes, format, ucbuf, ucbuf);
					}

					return t;
				}
			}

			// failure
			SAFE_DELETE_ARRAY(ucbuf);
			break;
		}
		else if(recordType == EXTENSION_RECORD_TYPE)
		{
			GifByteType *extension;
			int32 extCode;

			// Skip any extension blocks in file
			if(DGifGetExtension(gifinfo, &extCode, &extension) != GIF_ERROR)
			{
				while(extension != NULL)
				{
					if(DGifGetExtensionNext(gifinfo, &extension) == GIF_ERROR)
					{
						DGifCloseFile(gifinfo);
						return 0;
					}
				}
			}
			else
			{
				DGifCloseFile(gifinfo);
				return 0;
			}
		}
		break;
	}
	while (recordType != TERMINATE_RECORD_TYPE);

	DGifCloseFile(gifinfo);
	fclose(fp);

	return 0;
}

bool TextureManager::isScaleNeeded(const uint32 width, const uint32 height, uint32& scalew, uint32& scaleh)
{
	if(is2Multiplied(width) && is2Multiplied(height))
		return false;

	RenderDevice* rd = RenderDevice::GetInstancePtr();
	assert(rd);

	uint32 max_texture_size = rd->getSupportedTextureSize();

	scalew = getFloor2Multiflied(width);
	if(scalew > max_texture_size)
		scalew = max_texture_size;

	scaleh = getFloor2Multiflied(height);
	if(scaleh > max_texture_size)
		scaleh = max_texture_size;

	return true;
}

bool TextureManager::is2Multiplied(const uint32 value)
{
	uint32 shift = 0x1;
	uint32 bit = 0;
	for(uint32 i=0;i<32;i++)
	{
		if(value & (shift << i))
			bit++;
	}

	if(bit == 1)
		return true;

	return false;
}

uint32 TextureManager::getFloor2Multiflied(const uint32 value)
{
	uint32 shift = 0x1;
	uint32 new_value = 0;
	for(uint32 i=0;i<32;i++)
	{
		 new_value = shift << i;
		 if(new_value >= value)
			 return new_value;
	}

	return 0;
}
